package com.ruins.ping.controller

import com.ruins.ping.PingApplication
import com.ruins.ping.producer.LogSender
import org.redisson.api.RRateLimiter
import org.redisson.api.RedissonClient
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ContextConfiguration
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import spock.lang.Specification
import spock.lang.Unroll

@Unroll
@ContextConfiguration(classes = PingApplication.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PingControllerTest extends Specification {

    PingController pingController
    WebClient webClient = Mock(WebClient)
    RedissonClient redissonClient = Mock(RedissonClient)
    LogSender logSender = Mock(LogSender)
    RRateLimiter rateLimiter = Mock(RRateLimiter)

    @Value('${scheduler.enabled:true}')
    boolean isScheduledEnabled = false

    def setup() {
        pingController = new PingController()
        pingController.webClient = webClient
        pingController.redissonClient = redissonClient
        pingController.logSender = logSender

        redissonClient.getRateLimiter(_ as String) >> rateLimiter
    }

    def "sendPing should return 'already send' and log success when ping is successful"() {
        given:
        rateLimiter.tryAcquire() >> true
        def requestHeadersUriSpec = Mock(WebClient.RequestHeadersUriSpec)
        def responseSpec = Mock(WebClient.ResponseSpec)

        webClient.get() >> requestHeadersUriSpec
        requestHeadersUriSpec.uri("/pong") >> requestHeadersUriSpec
        requestHeadersUriSpec.retrieve() >> responseSpec
        responseSpec.bodyToMono(String.class) >> Mono.just("world")

        when:
        def response = pingController.sendPing()

        then:
        response == "already send"
        1 * logSender.sendLog("ping success: world")
    }

    def "sendPing should log error when ping fails"() {
        given:
        rateLimiter.tryAcquire() >> true
        def requestHeadersUriSpec = Mock(WebClient.RequestHeadersUriSpec)
        def responseSpec = Mock(WebClient.ResponseSpec)

        webClient.get() >> requestHeadersUriSpec
        requestHeadersUriSpec.uri("/pong") >> requestHeadersUriSpec
        requestHeadersUriSpec.retrieve() >> responseSpec
        responseSpec.bodyToMono(String.class) >> Mono.error(new RuntimeException("429"))

        when:
        pingController.sendPing()

        then:
        1 * logSender.sendLog("ping false: 429")
    }

    def "sendPing should return 'ping limit' when rate limit exceeded"() {
        given:
        rateLimiter.tryAcquire() >> false

        when:
        def response = pingController.sendPing()

        then:
        response == "ping limit"
        1 * logSender.sendLog("ping limit")
    }

    def "should set rate limiter configuration"() {
        when:
        pingController.tryAcquire()

        then: "Verifying that trySetRate is called once with expected parameters"
        1 * rateLimiter.trySetRate(_, 2, 1, _)
    }
}